/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */
package com.obixlabs.commons.security;

import java.util.List;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.obixlabs.commons.util.CharacterSource;
import com.obixlabs.commons.util.RandomCharacterGenerator;

/**
 * <p>
 * Base implementation of {@link PasswordRequirement}. 
 *</p>
 */
public abstract class AbstractPasswordRequirement implements PasswordRequirement
{
	private static final Logger logger = LoggerFactory.getLogger(AbstractPasswordRequirement.class);
	
	private static final int DEFAULT_PRIME = 19341;
	
	/**
	 * <p>
	 * Random number generator with which the requirement can 
	 * select {@link PasswordBuffer buffer} indices to mutate.
	 * This is generally used by {@link PasswordRequirement requirements}
	 * which are intended for strengthening candidate {@link PasswordBuffer 
	 * passwords}.  
	 * </p>
	 */
	private Random random;
	
	/**
	 * <p>
	 * The {@link CharacterSource source} from which this requirement fills
	 * a {@link #apply(PasswordBuffer) target buffer}.
	 * </p>
	 */
	private CharacterSource characterSource;
	
	/**
	 * <p>
	 * Default constructor which initialises an instance with a {@link RandomCharacterGenerator},
	 * which in turn can be used by subclasses when inserting relevant characters into {@link PasswordBuffer buffers}
	 * in order to strengthen them. This constructor also initialises the internal random index generator which 
	 * is required when {@link #insertIntoRandomIndexOrAppend(PasswordBuffer, char, boolean)  strengthening}
	 *  {@link PasswordBuffer buffers}.
	 * </p>
	 */
	public AbstractPasswordRequirement()
	{this(new RandomCharacterGenerator(), DEFAULT_PRIME); }

	/**
	 * <p>
	 * Similar to {@link #AbstractPasswordRequirement()} except that it allows the caller to override
	 * the default choice of {@link CharacterSource character data}. It, in effect, allows the caller
	 * to choose another {@link CharacterSource character datasource} as opposed to the 
	 * {@link RandomCharacterGenerator default}.
	 * </p>
	 * @param characterSource      The datasource to be used for password strengthening.
	 */
	public AbstractPasswordRequirement(CharacterSource characterSource)
	{	this(characterSource, DEFAULT_PRIME); }

	/**
	 * <p>
	 * Allows the caller full control over initialisation of the instance. The caller 
	 * can specify the {@link CharacterSource character source} as well as the seed 
	 * for initialising the internal {@link Random random number generator}; all of which
	 * play a part in the {@link #insertIntoRandomIndexOrAppend(PasswordBuffer, char, boolean) password strengthening}
	 * algorithm.
	 * </p>
	 * 
	 * @param characterSource      The {@link CharacterSource source} for character data which can be used 
	 * for password building strengthening.
	 * @param prime        The prime number with which the internal index generator can be initialised.
	 */
	public AbstractPasswordRequirement(CharacterSource characterSource, int prime)
	{
		super();
		this.characterSource = characterSource;
		this.random = new Random(System.currentTimeMillis()%prime);
	}
	
	/**
	 *<p>
	 * Accessor for the internal {@link Random random number generator} which can be used 
	 * for password strengthening/modification.
	 * </p>
	 * 
	 * @return     The internal {@link Random random number generator}. 
	 */
	public Random getRandom() {return random;}
	
        /**
         *<p>
         * Accessor for the internal {@link CharacterSource character source} which can be used 
         * for password strengthening/modification.
         * </p>
         * 
         * @return     The internal {@link CharacterSource character source}. 
         */
	public CharacterSource getCharacterSource() { return characterSource; }	
	
	/**
	 * <p>
	 * Strengthens the password encapsulated in the {@link PasswordBuffer password buffer} with the 
	 * the given character; either  by inserting it into, or appending it to,  the {@link PasswordBuffer buffer}.
	 * As a rule, this method will select one of the {@link PasswordBuffer#getWritableIndices() writable indices} in the buffer, at random,
	 * to insert the character into. Only if there are no {@link PasswordBuffer#getWritableIndices() writable indices} does 
	 * it then attempt to {@link PasswordBuffer#append(char) append } the character to it. 
	 * </p>
	 * 
	 * @param buffer       The {@link PasswordBuffer buffer} containing the password to be strengthened.
	 * @param character    The character with which the password is to be strengthened.
	 * @param overwritable Indicates if the changes made to the {@link PasswordBuffer buffer} are permanent or if 
	 * they can be overridden by another {@link PasswordRequirement password rule}.
	 *   
	 * @return     <code>True<code> if the {@link PasswordBuffer buffer} was successfully modified, i.e. if the new character
	 * could be inserted into one of the {@link PasswordBuffer#getWritableIndices() writable indices}, and in the case
	 * where there are none, that the character was appended successfully to the buffer. <code>False</code> otherwise.
	 */
	public boolean insertIntoRandomIndexOrAppend(PasswordBuffer buffer, char character, boolean overwritable)
	{
		boolean result;
		List<Integer> writableIndices = buffer.getWritableIndices();		
		if (!writableIndices.isEmpty())
		{
			if (logger.isDebugEnabled())
				logger.debug("Buffer has a few writable indices. Will insert at a random index.");
				
			int randomIndexKey = getRandom().nextInt(writableIndices.size());
			int randomIndex = writableIndices.get(randomIndexKey);
			buffer.overwrite(character, randomIndex, overwritable);
			result = true;
		}
		else
		{
			if (logger.isDebugEnabled())
				logger.debug("Buffer does not have any writable indices. Will have to append to end instead.");
			
			result = buffer.append(character, overwritable);		
		}
		return result;
	}	
}//end class def